/**
 * \file
 *         Ethernet MAC file. Moves up IPv6 packets into the stack and
 * 				 handles IPv4 - ARP interactions.
 *
 * \author
 *         Luis Maqueda <luis@sen.se>
 */

#include "net/mac/mac_eth_driver.h"
#include "net/p-gw/pgw_fwd.h"
#include "net/uipv4/uipv4_arp.h"
#include "contiki-net.h"
#include "net/uipv4/uipv4.h"

#define ETH_BUF ((struct uip_eth_hdr *)&uip_buf[0])
#define IPV4_BUF ((struct uipv4_ip_hdr *)&uip_buf[UIP_LLH_LEN])
#define IPV6_BUF ((struct uip_ip_hdr *)&uip_buf[UIP_LLH_LEN])

/* event timer to handle arp periodic invocations */
struct etimer mac_eth_periodic;

PROCESS(mac_eth_process, "mac_eth_process");

PROCESS_THREAD(mac_eth_process, ev, data)
{
	PROCESS_BEGIN();
	
	etimer_set(&mac_eth_periodic, CLOCK_SECOND * 10);
	while(1) {
    PROCESS_WAIT_EVENT();
    if(ev == PROCESS_EVENT_TIMER) {
      uipv4_arp_timer();
      etimer_restart(&mac_eth_periodic);
    }
  }
  
  PROCESS_END();
}

/*---------------------------------------------------------------------------*/
static void
send_packet()
{	
	if ((IPV4_BUF->vhl & 0xf0) == 0x40) {
		/* If the packet is an IPv4 packet generated by the local node,
		 * we ignore the values on src and dest, and let arp set the whole
		 * Ethernet header.
		 * Note that uipv4_arp_out() increases the value of uip_len with the
		 * length of the Ethernet header */
		uipv4_arp_out();
		/* And send the packet. Note that uipv4_arp_out() increases the value of
		 * uip_len with the length of the Ethernet header! */
		NETSTACK_ETHERNET.send(uip_buf, uip_len);
	} else if ((IPV6_BUF->vtc & 0xf0) == 0x60) {
		/* The Ethernet header was already built by pgw; increase the value of 
		 * uip_len and pass the packet to the lower layer */
		uip_len += sizeof(struct uip_eth_hdr);
		NETSTACK_ETHERNET.send(uip_buf, uip_len);
	} else {
		/* Error? */
		uip_len = 0;
		uip_ext_len = 0;
		return;
	}
}

/*---------------------------------------------------------------------------*/
static void
input_packet(void)
{
	/* Multiplex packet according to its version */
	if ((ETH_BUF->type == UIP_HTONS(UIP_ETHTYPE_IP)) && 
			((IPV4_BUF->vhl & 0xf0) == 0x40)) {
		/* IPv4. Update ARP cache and push the packet into the IPv4 stack. 
		 * Note that uipv4_arp_ipin() substracts the Ethernet header length from 
		 * uip_len */
		uipv4_arp_ipin();		
		NETSTACK_NETWORK_IPV4.input();
	} else if(ETH_BUF->type == UIP_HTONS(UIP_ETHTYPE_ARP)) {
		/* The packet is an ARP packet. 
		 * Note that uipv4_arp_arpin() expects uip_len to include the length of the 
		 * Ethernet header */
		uipv4_arp_arpin();
		if(uip_len > 0) {
			/* ARP generated an outgoing packet. Send it*/
			NETSTACK_ETHERNET.send(uip_buf, uip_len);
			/* Clear uip_len */
			uip_len = 0;
 		}
	} else if ((ETH_BUF->type == UIP_HTONS(UIP_ETHTYPE_IPV6)) &&
			((IPV6_BUF->vtc & 0xf0) == 0x60)) {
		/* IPv6. Substract the Ethernet header length from uip_buf and pass this 
		 * packet to the 6LP-GW logic */
		uip_len -= sizeof(struct uip_eth_hdr);
		NETSTACK_6LPGW.input(); 
	} else {
		/* Crap */
		uip_len = 0;
	}
}
/*---------------------------------------------------------------------------*/
static void
on(void)
{
  NETSTACK_ETHERNET.on();
}
/*---------------------------------------------------------------------------*/
static void
off(void)
{
  NETSTACK_ETHERNET.off();
}
/*---------------------------------------------------------------------------*/
static void
init(void)
{
	uipv4_arp_init();
	process_start(&mac_eth_process, NULL);
}
/*---------------------------------------------------------------------------*/
const struct mac_eth_driver mac_eth_driver = {
  "mac_eth_driver",
  init,
  send_packet,
  input_packet,
  on,
  off,
};
/*---------------------------------------------------------------------------*/
